import datetime
import sys
import numpy as np


class Sigmoid:
    def __init__(self):
        self.out = None

    def forward(self, x):
        out = 1 / (1 + np.exp(-x))
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * self.out * (1 - self.out)
        return dx


class MulLayer:
    def __init__(self, w, b):
        self.w = w
        self.b = b
        self.x = None
        self.dw = None
        self.db = None

    def update_weight(self, dw, db, lr):
        self.w -= lr * dw
        self.b -= lr * db

    def forward(self, x):
        self.x = x
        out = np.dot(x, self.w) + self.b
        return out

    def backward(self, dout):
        # dx = np.dot(dout, self.w.T)
        self.dw = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        return self.dw, self.db


class MeanSquareError:
    def __init__(self):
        self.y = None
        self.t = None
        self.size = None

    def forward(self, y, t):
        self.y, self.t, self.size = y, t, t.shape[0]
        return 0.5 * np.power(t - y, 2).sum() / self.size

    def backward(self, dout=1.0):
        dy = dout * (self.y - self.t) / self.size
        return dy


class OneLayerNet:
    def __init__(self, train_file_name, test_file_name, predict_result_file_name):
        self.train_file = train_file_name  # 训练集所在文件路径
        self.predict_file = test_file_name  # 需要预测的文件所在路径
        self.predict_result_file = predict_result_file_name  # 预测结果存放的文件
        self.max_iters = 2500  # 训练的迭代次数
        self.rate = 0.04  # 学习率
        self.feats = []  # 训练时的数据特征
        self.labels = []  # 训练时的数据标签
        self.feats_test = []  # 测试时的数据特征
        self.labels_predict = []  # 测试时预测的数据标签
        self.param_num = 0  # 单个样本的特征维度
        self.mul = None
        self.sigmoid = None
        self.mse = None

    def loadDataSet(self, file_name, label_existed_flag):
        start = datetime.datetime.now()
        feats = []
        labels = []
        fr = open(file_name)
        lines = fr.readlines()
        for line in lines:
            temp = []
            allInfo = line.strip().split(',')
            dims = len(allInfo)
            # 文件中有标签时，最有一列为标签
            if label_existed_flag == 1:
                for index in range(dims - 1):
                    temp.append(float(allInfo[index]))
                feats.append(temp)
                labels.append(float(allInfo[dims - 1]))
            else:
                for index in range(dims):
                    temp.append(float(allInfo[index]))
                feats.append(temp)
        fr.close()
        feats = np.array(feats)
        labels = np.array(labels)
        end = datetime.datetime.now()
        print("loadTime:", (end - start).total_seconds())
        return feats, labels

    def loadTrainData(self):
        self.feats, self.labels = self.loadDataSet(self.train_file, 1)

    def loadTestData(self):
        self.feats_test, self.labels_predict = self.loadDataSet(self.predict_file, 0)

    def savePredictResult(self):
        start = datetime.datetime.now()
        print(self.labels_predict)
        f = open(self.predict_result_file, 'w')
        for i in range(len(self.labels_predict)):
            f.write(str(self.labels_predict[i]) + "\n")
        f.close()
        end = start = datetime.datetime.now()
        print("savingTime:", (start - end).total_seconds())

    def printInfo(self):
        print(self.train_file)
        print(self.predict_file)
        print(self.predict_result_file)
        print(self.feats)
        print(self.labels)
        print(self.feats_test)
        print(self.labels_predict)

    def initParams(self):
        w = np.ones((self.param_num,), dtype=np.float)
        b = np.zeros(1)
        self.mul = MulLayer(w, b)
        self.sigmoid = Sigmoid()
        self.mse = MeanSquareError()

    def compute(self, feats):
        a = self.mul.forward(feats)
        y = self.sigmoid.forward(a)
        return y

    def gradient_descent(self, dout):
        # dout = self.sigmoid.backward(dout)
        dw, db = self.mul.backward(dout)
        self.mul.update_weight(dw, db, self.rate)

    def predict(self):
        self.loadTestData()
        preval = self.compute(self.feats_test)
        self.labels_predict = (preval + 0.5).astype(np.int)
        self.savePredictResult()

    def train(self):
        self.loadTrainData()
        start = datetime.datetime.now()
        self.param_num = len(self.feats[0])
        self.initParams()
        ISOTIMEFORMAT = '%Y-%m-%d %H:%M:%S,f'  # 定义时间格式
        for i in range(self.max_iters):
            preval = self.compute(self.feats)
            sum_loss = self.mse.forward(preval, self.labels)
            dout = self.mse.backward(dout=1.0)
            self.gradient_descent(dout)
            # 每迭代训练30次，打印一次信息
            if i % 30 == 0:
                theTime = datetime.datetime.now().strftime(ISOTIMEFORMAT)
                print("Iters->" + str(i) + "  loss:" + str(sum_loss) + '  ' +theTime)
        end = datetime.datetime.now()
        print("trainingTime:", (end - start).total_seconds())


def print_help_and_exit():
    print("usage:python3 main.py train_data.txt test_data.txt predict.txt [debug]")
    sys.exit(-1)


def parse_args():
    debug = False
    if len(sys.argv) == 2:
        if sys.argv[1] == 'debug':
            print("test mode")
            debug = True
        else:
            print_help_and_exit()
    return debug


if __name__ == "__main__":
    debug = parse_args()
    train_file =  "data/train_data.txt"
    test_file = "data/test_data.txt"
    predict_file = "projects/student/result.txt"
    lr = OneLayerNet(train_file, test_file, predict_file)
    lr.train()
    lr.predict()

    if debug:
        answer_file ="projects/student/answer.txt"
        f_a = open(answer_file, 'r')
        f_p = open(predict_file, 'r')
        a = []
        p = []
        lines = f_a.readlines()
        for line in lines:
            a.append(int(float(line.strip())))
        f_a.close()

        lines = f_p.readlines()
        for line in lines:
            p.append(int(float(line.strip())))
        f_p.close()

        print("answer lines:%d" % (len(a)))
        print("predict lines:%d" % (len(p)))

        errline = 0
        for i in range(len(a)):
            if a[i] != p[i]:
                errline += 1

        accuracy = (len(a)-errline)/len(a)
        print("accuracy:%f" %(accuracy))